{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Katz Centrality\n",
    "\n",
    "In this notebook, we will compute the Katz centrality of each vertex in our test datase using both cuGraph and NetworkX. Additionally, NetworkX also contains a Numpy implementation that will used. The NetworkX and cuGraph processes will be interleaved so that each step can be compared.\n",
    "\n",
    "Notebook Credits\n",
    "* Original Authors: Bradley Rees\n",
    "* Created:   10/15/2019\n",
    "* Last Edit: 02/27/2020\n",
    "\n",
    "RAPIDS Versions: 0.13   \n",
    "\n",
    "Test Hardware\n",
    "\n",
    "* GV100 32G, CUDA 10.2\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Introduction\n",
    "Katz centrality is a measure of the relative importance of a vertex within the graph based on measuring the influence across the total number of walks between vertex pairs. \n",
    "\n",
    "<img src=\"https://latex.codecogs.com/gif.latex?C_{katz}(i)&space;=&space;\\sum_{k=1}^{\\infty}&space;\\sum_{j=1}^{n}&space;\\alpha&space;^k(A^k)_{ji}\" title=\"C_{katz}(i) = \\sum_{k=1}^{\\infty} \\sum_{j=1}^{n} \\alpha ^k(A^k)_{ji}\" />\n",
    "\n",
    "See [Katz on Wikipedia](https://en.wikipedia.org/wiki/Katz_centrality) for more details on the algorithm.\n",
    "\n",
    "To compute the Katz centrality scores for a graph in cuGraph we use:<br>\n",
    "__df = cugraph.katz_centralityu(G,alpha=0.1, max_iter=100, tol=1.0e-6, nstart=None, normalized=True)__\n",
    "\n",
    "    G: cugraph.Graph object\n",
    "    alpha: float, Attenuation factor. default is 0.1\n",
    "    max_iter: int, The maximum number of iterations before an answer is returned. \n",
    "            This can be used to limit the execution time and do an early exit \n",
    "            before the solver reaches the convergence tolerance. If this value \n",
    "            is lower or equal to 0 cuGraph will use the default value, which is 100\n",
    "    tol: float, Set the tolerance the approximation, this parameter should be a small \n",
    "            magnitude value. The lower the tolerance the better the approximation. If \n",
    "            this value is 0.0f, cuGraph will use the default value which is 0.00001. \n",
    "            Setting too small a tolerance can lead to non-convergence due to numerical \n",
    "            roundoff. Usually values between 0.01 and 0.00001 are acceptable.\n",
    "    nstart:cuDataFrame, GPU Dataframe containing the initial guess for katz centrality. \n",
    "            Default is None\n",
    "    normalized:bool, If True normalize the resulting katz centrality values.  \n",
    "            Default is True\n",
    "\n",
    "Returns:\n",
    "\n",
    "    df: a cudf.DataFrame object with two columns:\n",
    "        df['vertex']: The vertex identifier for the vertex\n",
    "        df['pagerank']: The pagerank score for the vertex\n",
    "\n",
    "\n",
    "\n",
    "The value of _alpha_ should be<br>\n",
    "<img src=\"https://latex.codecogs.com/gif.latex?\\alpha&space;<&space;\\frac{1}{\\lambda&space;_{max}&space;}\" title=\"\\alpha < \\frac{1}{\\lambda _{max} }\" />\n",
    "currently the user is responsible for setting alpha appropiatly.  \n",
    "\n",
    "### _NOTICE_\n",
    "There is a difference between how cuGraph and how NetworkX computes the Katz centrality score.  That difference leads to the scores not matching.  cuGraph does not currently support the 'beta' and 'weight' parameters as seen in the corresponding networkX call. The cuGraph implementation is based on a relaxed version of Katz defined by Foster with a reduced computational complexity of O(n+m)\n",
    "\n",
    "    Foster, K.C., Muth, S.Q., Potterat, J.J. et al.\n",
    "    Computational & Mathematical Organization Theory (2001) 7: 275.\n",
    "    https://doi.org/10.1023/A:1013470632383\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### cuGraph Notice \n",
    "The current version of cuGraph has some limitations:\n",
    "\n",
    "* Vertex IDs need to be 32-bit integers.\n",
    "* Vertex IDs are expected to be contiguous integers starting from 0\n",
    "\n",
    "cuGraph provides the renumber function to mitigate this problem. Input vertex IDs for the renumber function can be of any data type and do not need to be contiguous. The renumber function maps the provided input vertex IDs to 32-bit contiguous integers starting from 0. cuGraph still requires the renumbered vertex IDs to be representable in 32-bit integers. These limitations are being addressed and will be fixed soon.    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Test Data\n",
    "We will be using the Zachary Karate club dataset \n",
    "*W. W. Zachary, An information flow model for conflict and fission in small groups, Journal of\n",
    "Anthropological Research 33, 452-473 (1977).*\n",
    "\n",
    "\n",
    "![Karate Club](../img/zachary_black_lines.png)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Prep"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import needed libraries\n",
    "import cugraph\n",
    "import cudf\n",
    "from collections import OrderedDict"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# NetworkX libraries\n",
    "import networkx as nx"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Some Prep"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# define the parameters \n",
    "max_iter = 100  # The maximum number of iterations\n",
    "tol = 0.00001   # tolerance"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define the path to the test data  \n",
    "datafile='../data/karate-data.csv'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Read in the data - GPU\n",
    "cuGraph depends on cuDF for data loading and the initial Dataframe creation\n",
    "\n",
    "The data file contains an edge list, which represents the connection of a vertex to another.  The `source` to `destination` pairs is in what is known as Coordinate Format (COO).  In this test case, the data is just two columns.  However a third, `weight`, column is also possible"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "gdf = cudf.read_csv(datafile, delimiter='\\t', names=['src', 'dst'], dtype=['int32', 'int32'] )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Create a Graph "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "# create a Graph using the source (src) and destination (dst) vertex pairs from the Dataframe \n",
    "G = cugraph.Graph()\n",
    "G.from_cudf_edgelist(gdf, source='src', destination='dst')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# compute degree and get the max\n",
    "degree = G.degrees()\n",
    "lamda = degree['out_degree'].max()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The max degree is 17\n"
     ]
    }
   ],
   "source": [
    "print(\"The max degree is \" + str(lamda))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Call the Karz algorithm"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "alpha = 1 / lamda"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Call cugraph.katz_centrality to get the Katz scores\n",
    "gdf_katz = cugraph.katz_centrality(G, alpha=alpha)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "_It was that easy!_  \n",
    "\n",
    "----\n",
    "\n",
    "Let's now look at the results"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Find the most important vertex using the scores\n",
    "# This methods should only be used for small graph\n",
    "def find_top_scores(_df) :\n",
    "    m = _df['katz_centrality'].max()\n",
    "    return _df.query('katz_centrality >= @m')\n",
    "        "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>vertex</th>\n",
       "      <th>katz_centrality</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>33</th>\n",
       "      <td>34</td>\n",
       "      <td>0.433606</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "    vertex  katz_centrality\n",
       "33      34         0.433606"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "top_df = find_top_scores(gdf_katz)\n",
    "top_df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>vertex</th>\n",
       "      <th>katz_centrality</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>33</th>\n",
       "      <td>34</td>\n",
       "      <td>0.433606</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>1</td>\n",
       "      <td>0.416119</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>32</th>\n",
       "      <td>33</td>\n",
       "      <td>0.327817</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>3</td>\n",
       "      <td>0.296538</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>2</td>\n",
       "      <td>0.256625</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "    vertex  katz_centrality\n",
       "33      34         0.433606\n",
       "0        1         0.416119\n",
       "32      33         0.327817\n",
       "2        3         0.296538\n",
       "1        2         0.256625"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# let's sort the data and look at the top 5 vertices\n",
    "gdf_katz.sort_values(by='katz_centrality', ascending=False).head(5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Now compute using NetworkX"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Read the data, this also created a NetworkX Graph \n",
    "file = open(datafile, 'rb')\n",
    "Gnx = nx.read_edgelist(file)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "k_nx = nx.katz_centrality(Gnx, alpha=alpha, max_iter=max_iter, tol=tol)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "k_nx_s = sorted(((value, key) for (key,value) in k_nx.items()), reverse=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(0.27758732063256364, '34'),\n",
       " (0.2709998485802582, '1'),\n",
       " (0.23773581168671487, '33'),\n",
       " (0.22595292741481365, '3'),\n",
       " (0.21091768975920322, '2')]"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "k_nx_s[:5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As mentioned, the scores are different but the ranking is the same."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "# The Numpy version\n",
    "k_nx_mp = nx.katz_centrality_numpy(Gnx, alpha=alpha)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(0.2775875628109348, '34'),\n",
       " (0.27100006570440377, '1'),\n",
       " (0.23773599523859307, '33'),\n",
       " (0.22595315380272363, '3'),\n",
       " (0.21091783569614825, '2')]"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sorted(((value, key) for (key,value) in k_nx_mp.items()), reverse=True)[:5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "___\n",
    "Copyright (c) 2019, NVIDIA CORPORATION.\n",
    "\n",
    "Licensed under the Apache License, Version 2.0 (the \"License\");  you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0\n",
    "\n",
    "Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.\n",
    "___"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "cugraph_dev",
   "language": "python",
   "name": "cugraph_dev"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
